package edu.towson.greenteam.order;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import edu.towson.greenteam.ClockTick;
import edu.towson.greenteam.Collection;
import edu.towson.greenteam.Consts;
import edu.towson.greenteam.order.strategy.IOrderPrepStrategy;
import edu.towson.greenteam.Invoker;
import edu.towson.greenteam.Iterator;
import edu.towson.greenteam.Logger;
import edu.towson.greenteam.tab.Tab;
import edu.towson.greenteam.table.Table;
import edu.towson.greenteam.menu.MenuItem;

/* Orders is observed by:
 * 1.  Tables (to check if the most recently dequeued order completes all of the 
 *     orders for a given table)
 * 2.  Menu (to update the currentQty of the most recently added order's MenuItem)
 * 
 * Orders is an observer of CustomerQueue: 
 * 1.  When a customer is dequeued, 3 orders per person in party will be created and 
 *     enqueued into Orders
 *     
 */
public class Orders extends Observable implements Observer, Collection<Order>, ClockTick // This for all intents and purposes is our Orders queue (or OrdersQueue)
{  
    private ArrayList<Observer> observers;
	private Invoker i = null;
	private IOrderPrepStrategy strategy;
	
    public ArrayList<Order> currentOrders;
    private int ctcount = 0;   //clock tick count
    private int lastID;
    
    private static int totalNumOrders=0;
    private static int totalPrepTime=0;

    private static int maxOrders = 0;
    private static double minOrders = Double.POSITIVE_INFINITY;

    public Orders() {
    	observers = new ArrayList<Observer>(); 
        currentOrders = new ArrayList<Order>();
        lastID = 0;
        
        //default strategy
        setStrategy(new OrderPrepFIFOStrategy());
//        setStrategy(new OrderPrepShortestPrepFirst());
    }

    @Override
    public boolean add(Order o) {
        if (this.isEmpty()) {
            currentOrders.add(o);
            o.setId(lastID++);
        } else {
            if (this.contains(o)) {
                return false;
            }
            currentOrders.add(o);
            o.setId(lastID++);
        }
        return true;
    }
    

	public boolean add(int id, String itemName, float currentPrice, Table tableOrderPlacedFrom) {
		Order newOrder = new Order(id, itemName, currentPrice, tableOrderPlacedFrom);
		return currentOrders.add(newOrder);
	}
	
	

	@Override
	public boolean update(Order e) {
		OrderAllItr orderItr = new OrderAllItr(this);
		while (orderItr.hasNext()) {
			Order foundOrder = orderItr.next();
			if (foundOrder == e) {
				currentOrders.remove(currentOrders.indexOf(foundOrder));
				currentOrders.add(e);
				return true;
			}
		}
		return false;
	}

    @Override
    public boolean contains(Order e) {
        return currentOrders.contains(e);
    }

    @Override
    public boolean remove(Order e) {
        return currentOrders.remove(e);
    }

    @Override
    public boolean isEmpty() {
        return currentOrders.isEmpty();
    }

	@Override
	public int size() {
		return currentOrders.size();
	}

	public Order get(int i) {
		//check to make sure i is less than size
		if (currentOrders.size() > i) {
			return currentOrders.get(i);
		}
		else {
			return null;
		}
	}

        public static int getMaxOrders() {
            return maxOrders;
        }

        public static int getMinOrders() {
            return (int) minOrders;
        }
	
	private void placeOrder(MenuItem orderedItem, Table assignedTable) {
		Order newOrder = new Order(orderedItem, orderedItem.getCurrentPrice(), assignedTable);
		add(newOrder);
		assignedTable.addOrder(newOrder);
		notifyObservers(newOrder);
	}
	
	public void setInvoker(Invoker i) {
		this.i = i;
	}
	
	public Iterator<Order> createOrderAllItr(Orders allOrders) {
		return new OrderAllItr(allOrders);
	}
	
	public Iterator<Order> createOrderNotPreparedItr() {
		return new OrderNotPreparedItr(this);
	}
	
		
	/************************************************************************
	 * Methods for being OBSERVABLE (i.e. Orders observed by Tables,
	 *                                    Orders observed by Menu
	 ***********************************************************************/
    @Override 
    public void addObserver(Observer o) { 
        this.observers.add(o); 
    }
    
    @Override
    public void notifyObservers(Object arg) {
    	for (int i=0; i<observers.size(); i++) {
    		Observer o = (Observer) observers.get(i);
    		o.update(this, arg);
    	}     	  	 
    }
    
    /************************************************************************
     * Methods for being OBSERVER (i.e. Orders listens to CustomerQueue)
     ***********************************************************************/
    @Override
    public void update(Observable arg0, Object arg1) {
    	String changedClass = arg0.getClass().getCanonicalName();
    	if (changedClass == Consts.CUSTOMERQ) {
	        Object[] arg = (Object[]) arg1;
	        int numOrdersNeeded = (Integer) arg[0] * Consts.NUM_MENUITEMS_PER_CUST; //multiply # custs w/ random num to produce orders for a table
	        if (numOrdersNeeded > maxOrders) {
                    maxOrders = numOrdersNeeded;
                }
                if (numOrdersNeeded < minOrders) {
                    minOrders = numOrdersNeeded;
                }
                Table tableJustSeated1 = (Table) arg[1];
	        Table tableJustSeated2=null;
	        if (arg[2] != null)
	        	tableJustSeated2 = (Table) arg[2];
	        for (int i = 0; i < numOrdersNeeded; i++) {
	            MenuItem m = getRandomMenuItem();
	            if (m != null) {
		        	if (i < Consts.NUM_MENUITEMS_PER_CUST*Consts.NUM_SEATS_PER_TABLE) {
		        		placeOrder(m, tableJustSeated1);
		        		Logger.GetInstance().LogMessage("TABLE: " + tableJustSeated1.getId() + " Ordered " + m.getName());
		        	} else {
		        		if (tableJustSeated2 != null) {
		        			placeOrder(m, tableJustSeated2);
		        			Logger.GetInstance().LogMessage("TABLE: " + tableJustSeated2.getId() + " Ordered " + m.getName());
		        		}
		        	}
	            }
	        }
    	}
    }

	private MenuItem getRandomMenuItem() {
        //TODO: figure out a way to make it more biased towards cheaper items
		int count = 0;
		
		int max=i.getMenu().size();
		
		Random rand = new Random();
		int pickedNumber = rand.nextInt(max); 
		Iterator<MenuItem> menuItemItr = i.getMenu().getItr();
		while (menuItemItr.hasNext())
		{
			MenuItem currentItem = menuItemItr.next();
			if (count == pickedNumber)
			{
				return currentItem;
			}
			count++;
		}
		return null;
	}

	@Override
    // Getting this started...needs work
    public void clockTick() {
        ctcount++;
        Logger.GetInstance().LogMessage("Order clocktick " + ctcount);
        if (strategy != null) {
        	Order OrderJustPrepared = null;
        	if ((OrderJustPrepared = (Order) strategy.dequeue()) != null) {     
        		totalNumOrders++;  //track num orders & prep time for avg prep time
                totalPrepTime += OrderJustPrepared.getMenuItem().getPrepTime();
                Logger.GetInstance().LogMessage(Consts.newline+"tablePrep: " + OrderJustPrepared.getTable().getId() + 
                		                           " Order#:" + OrderJustPrepared.getId() + " " + OrderJustPrepared.getMenuItemName() );
                Logger.GetInstance().LogMessage("****AvgPrepTime: " + getAvgPrepTime()+Consts.newline);
                setCtcount(0);
                setChanged();
                if (allItemsPrepared(OrderJustPrepared.getTable())) {
                	setAllItemsServed(OrderJustPrepared.getTable()); //set all items served
                	Logger.GetInstance().LogMessage("SERVED TABLE: " + OrderJustPrepared.getTable().getId());
                    
                	Tab newTab = createTab(OrderJustPrepared.getTable()); //make tab set as paid
                	newTab.setPaymentstatus(true);
                	i.getTabs().add(newTab);
                	Logger.GetInstance().LogMessage("PAID TABLE: " + OrderJustPrepared.getTable().getId());
                    //remove Orders from Table
                	OrderJustPrepared.getTable().clearOrders();
                    //remove Orders from Queue that were just prepared
                    RemoveOrdersfromQueue(OrderJustPrepared.getTable());
                    
                    notifyObservers(OrderJustPrepared);
                }
        	}
        }
    }

	private boolean setAllItemsServed(Table table)
	{
		ArrayList<Order> allOrders = table.getAllTablesOrders();
		if (allOrders.size() == 0)
		{
			return false; //return false if there are no orders at the table
		}
		for (int i = 0; i < allOrders.size(); i++)
		{
			allOrders.get(i).setServed(true);
		}
		return true; //return true if everything was successful
	}

	private Tab createTab(Table table)
	{
		//new tab from table pin
		Tab returnTab = new Tab(table.getPin()); 
		
		//add all orders
		ArrayList<Order> allOrders = table.getAllTablesOrders();		
		for (int i = 0; i < allOrders.size(); i++)
		{
			returnTab.addMenuItem(allOrders.get(i));
		}
		
		return returnTab; //return true if everything was successful
	}
	
	private boolean allItemsPrepared(Table table)
	{
		ArrayList<Order> allOrders = table.getAllTablesOrders();
		if (allOrders.size() == 0)
		{
			return false; //return false if there are no orders at the table
		}
		for (int i = 0; i < allOrders.size(); i++)
		{
			if(!allOrders.get(i).isPrepared())
			{
				return false; //return false if you find one that isn't prepared
			}
		}
		return true; //return true otherwise
	}
	
	private void RemoveOrdersfromQueue(Table table) {
		
		for (int i = 0; i < currentOrders.size(); i++)
		{
			if (currentOrders.get(i).getTable().getId() == table.getId()) {
				currentOrders.remove(i);
				i--;
			}
		}
	}
	
	
	public IOrderPrepStrategy getStrategy() {
		return strategy;
	}

	public void setStrategy(IOrderPrepStrategy strategy) {
		this.strategy = strategy;
	}
	
	public int getCtcount() {
		return ctcount;
	}

	public void setCtcount(int ctcount) {
		this.ctcount = ctcount;
	}
	
	@Override
	public String toString() {
		String sReturn = new String();
		// Display orders 
		for (int i=0; i<currentOrders.size(); i++)
		{
			Order ord = (Order) currentOrders.get(i);
			String fsOrder;
			fsOrder = String.format("Id:%d   Tbl:%d   MenuItem: %s   Price: $%.2f", 
					ord.getId(), ord.getTable().getId(), ord.getMenuItemName(), ord.getPrice());
			sReturn = sReturn.concat(fsOrder + Consts.newline);
		}
		return sReturn;
	}
	
	public static String getAvgPrepTime() {
		float avgpreptime;
		avgpreptime = (float) totalPrepTime / (float) totalNumOrders;
		return Float.toString(avgpreptime);
	}
	
	

	private class OrderPrepFIFOStrategy implements IOrderPrepStrategy {

		@Override
		public Object dequeue() {
			Order rtnOrder = null;
			Order firstNotPreparedOrder = GetFirstNotPreparedOrder();
			if (!currentOrders.isEmpty() && firstNotPreparedOrder != null) {
	        	
	            if (firstNotPreparedOrder.getMenuItem().getPrepTime() == getCtcount()) {
	                firstNotPreparedOrder.setPrepared(true);
	                rtnOrder = firstNotPreparedOrder;
	            } else if (firstNotPreparedOrder.getMenuItem().getPrepTime() < getCtcount())
	            	setCtcount(0);
	        }
			return rtnOrder;
		}   
		
		private Order GetFirstNotPreparedOrder() {
			Iterator<Order> notPreparedItr = createOrderNotPreparedItr();
	    	Order firstNotPreparedOrder= null;
	    	if (notPreparedItr.hasNext()) {
	    		firstNotPreparedOrder = notPreparedItr.next();
	    	}
	    	return firstNotPreparedOrder;
		}
				
	}  //OrderPrepFIFOStrategy
	
	
	public class OrderPrepShortestPrepFirst implements IOrderPrepStrategy {	
		
		int skipcount=0;
		boolean switchOrders=false;

	    @Override
	    public Object dequeue() {
	    	Order rtnOrder = null;
	    	switchOrders=false;
			Order notPrepdOrder = GetShortestTimeNotPrepOrder();
			if (!currentOrders.isEmpty() && notPrepdOrder != null) {
	        	
	            if (notPrepdOrder.getMenuItem().getPrepTime() == getCtcount()) {
	            	notPrepdOrder.setPrepared(true);
	                if (switchOrders) 
	                	skipcount++;
	                else
	                	skipcount=0;
	                
	                rtnOrder = notPrepdOrder;
	                
	            } else if (notPrepdOrder.getMenuItem().getPrepTime() < getCtcount())
	            	setCtcount(0);
	        }
			return rtnOrder;
	    }
	    
	    private Order GetShortestTimeNotPrepOrder() {
	    	
	    	Order rtnOrder=null;
	    	Order firstNotPrepOrder= null;
	    	Order secondNotPrepOrder=null;
	    	
			Iterator<Order> notPreparedItr = createOrderNotPreparedItr();
			
	    	if (notPreparedItr.hasNext()) {
	    		firstNotPrepOrder = notPreparedItr.next();
	    		if (skipcount == 5) {   //20 is a random number
	    			rtnOrder = firstNotPrepOrder;
	    		}	
	    		else {   //compare first two orders
		    		if (notPreparedItr.hasNext()) {
		    			secondNotPrepOrder = notPreparedItr.next();
		    			if (secondNotPrepOrder.getMenuItem().getPrepTime() < firstNotPrepOrder.getMenuItem().getPrepTime()) {
		    				rtnOrder = secondNotPrepOrder;
		    				switchOrders=true;
		    			}
		    			else 
		    				rtnOrder = firstNotPrepOrder;			
		    		}
		    		else
		    			rtnOrder=firstNotPrepOrder;
	    		}
	    	}
	    	return rtnOrder;
		}

	}  //OrderPrepShortestPrepFirst



	
	
	
}
